package network

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"net"
)

// 对TCP消息进行编解码，解决粘包问题
var (
	errCanceled      = errors.New("operation was canceled")
	errWSAECONNRESET = errors.New("wsarecv: An existing connection was forcibly closed by the remote host.")
	errNetClosing    = errors.New("use of closed network connection")
)

// Encode 将消息编码.miao
func Encode(msg any) ([]byte, error) {
	data, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}
	// 读取消息的长度，转换成int32类型（占4个字节）
	length := uint32(len(data))
	pkg := new(bytes.Buffer)
	// 写入消息头
	err = binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// 写入消息实体
	err = binary.Write(pkg, binary.LittleEndian, data)
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}

func Decode2(reader *bufio.Reader) (data []byte, err error) {
	var lengthByte []byte
	// 读取前4个字节的数据，
	// 这里 Peek() 翻译成中文就是 窥视、偷看的意思，
	// 他和Read()的区别是 Peek()不移动读取指针，而Read()会移动数据源的读取指针。
	lengthByte, err = reader.Peek(4)
	if err != nil {
		return
	}
	// 将读取到的4个字节放入一个buf中，因为这个时候的4个字节是 []byte，还不是一个int32
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length uint32
	// 将那个装载了4字节的buf里面的内容读取出来，填充到 var length int32 中，这个时候才能得到真的长度
	err = binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return
	}
	logrus.Debugf("消息长度: %d", length+4)
	// 读取缓冲区中的全部数据，因为第一次读取时用的 Peek()，指针没有移动过，因此这次要读取 4+length 的数据才是全部
	data = make([]byte, int(4+length))
	var n int
	n, err = reader.Read(data)
	if err != nil {
		return
	}
	if n == len(data) {
		return data[4:], nil
	} else {
		// 说明数据比较大，缓冲区一次放不下，需要循环读取
		// windows 下socket 缓冲区默认8K，因此数据超过8192字节就一定会粘包。
		data = data[:n]
		var pianIndex int
		for {
			var (
				pian []byte
				n1   int
			)
			// 每一 pian 的大小固定为1024，为了避免读超，这里判断一下。
			// 不然缓冲区中出现粘包的时候会将后面一个包的前面一部分也取出来
			if int(4+length)-len(data) > 1024 {
				pian, err = reader.Peek(1024)
			} else {
				pian, err = reader.Peek(int(4+length) - len(data))
			}
			pianIndex++
			n1, err = reader.Read(pian)
			if err != nil {
				fmt.Println("pianIndex", pianIndex)
				fmt.Println("Read", err)
				return
			}
			if n1 == 0 {
				fmt.Println("pianIndex", pianIndex)
			}
			if n1 > 0 {
				data = append(data, pian[:n1]...)
			}
			if len(data) == int(4+length) {
				return data[4:], nil
			}
			if len(data) > int(4+length) {
				// 正常情况下这里走不到
				err = errors.New("不是按hcy的Encode()编码的数据,或者计划之外的情况！")
				return data, err
			}
		}
	}
}

// Decode 解码
func Decode(conn net.Conn) (data []byte, err error) {
	var lengthByte []byte
	// 读取前4个字节的数据，
	// 这里 Peek() 翻译成中文就是 窥视、偷看的意思，
	// 他和Read()的区别是 Peek()不移动读取指针，而Read()会移动数据源的读取指针。
	lengthByte = make([]byte, 4)
	_, err = conn.Read(lengthByte)
	//lengthByte, err = reader.Peek(4)
	if err != nil {
		return
	}

	// 将读取到的4个字节放入一个buf中，因为这个时候的4个字节是 []byte，还不是一个int32
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length uint32
	// 将那个装载了4字节的buf里面的内容读取出来，填充到 var length int32 中，这个时候才能得到真的长度
	err = binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return
	}
	// 读取缓冲区中的全部数据，因为第一次读取时用的 Peek()，指针没有移动过，因此这次要读取 4+length 的数据才是全部
	data = make([]byte, length)
	var n int
	n, err = conn.Read(data)
	if err != nil {
		return
	}
	if n == len(data) {
		return data, nil
	} else {
		// 说明数据比较大，缓冲区一次放不下，需要循环读取
		// windows 下socket 缓冲区默认8K，因此数据超过8192字节就一定会粘包。
		data = data[:n]
		for {
			var (
				pian []byte
				n1   int
			)
			// 每一 pian 的大小固定为1024，为了避免读超，这里判断一下。
			// 不然缓冲区中出现粘包的时候会将后面一个包的前面一部分也取出来
			if length-uint32(len(data)) >= 1024 {
				pian = make([]byte, 1024)
			} else {
				pian = make([]byte, length-uint32(len(data)))
			}
			n1, err = conn.Read(pian)
			if err != nil {
				return
			}
			if n1 > 0 {
				data = append(data, pian[:n1]...)
			}
			if uint32(len(data)) == length {
				return data, nil
			}
			if uint32(len(data)) > length {
				// 正常情况下这里走不到
				err = errors.New("不是按hcy的Encode()编码的数据,或者计划之外的情况！")
				return data, err
			}
		}
	}
}

// needReadError 获取IO 错误类型，这里为将来的句柄复用做准备，
// // 例如消息重连等等，参考微信或者其他视频直播软件等，画面卡主回复后客户端会集中收到一段很快的画面的效果
func needReadError(err error) bool {
	var oe = &net.OpError{}
	if !errors.As(err, &oe) {
		return true
	}
	if errors.Is(err, errCanceled) {
		return false
	}
	if oe.Err.Error() == errWSAECONNRESET.Error() {
		return true
	}
	if oe.Err.Error() == errNetClosing.Error() {
		return false
	}
	return false
}
